/*
实现深响应
 */

import { track, trigger, ITERATE_KEY, TriggerType } from './reactive-system'

function createReactive(obj, isShallow = false, isReadonly = false) {
  return new Proxy(obj, {
    // in 操作的拦截
    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },
    // for... in ...拦截
    ownKeys(target) {
      track(target, ITERATE_KEY)
      return Reflect.ownKeys(target)
    },
    // 拦截删除属性操作
    deleteProperty(target, key) {
      if (isReadonly) {
        console.warn(`属性${key}是只读`)
        return true
      }
      const hasKey = Object.prototype.hasOwnProperty.call(target, key) // 判断是否是自身属性
      const res = Reflect.deleteProperty(target, key)
      if (res && hasKey) {
        trigger(target, key, TriggerType.DELETE)
      }
      return res
    },
    set(target, key, val, receiver) {
      if (isReadonly) {
        console.warn(`属性${key}是只读的`)
        return true
      }
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? TriggerType.SET
        : TriggerType.ADD // 要区分是新增属性还是修改属性
      const oldVal = target[key]
      const res = Reflect.set(target, key, val, receiver)
      // 代理对象指向就是原对象的时候， 从而达到屏蔽原型引起的重复更新
      if (receiver.raw === target) {
        // 当新旧值不同且 有一个不是NaN的时候，触发响应 (主要是防止两次都是NaN,但是NaN !== NaN)
        // eslint-disable-next-line no-self-compare
        if (oldVal !== val && (oldVal === oldVal || val === val)) {
          trigger(target, key, type)
        }
      }
      return res
    },
    get(target, key, receiver) {
      // 方便合理触发响应，判断receiver.raw
      if (key === 'raw') {
        return target
      }
      // 非只读的时候才需要建立响应式
      if (!isReadonly) {
        track(target, key)
      }
      const res = Reflect.get(target, key, receiver)
      // 浅响应，直接返回原始值
      if (isShallow) return res
      if (typeof res === 'object' && res !== null) {
        return isReadonly ? readonly(res) : reactive(res)
      }
      return res
    },
  })
}

export function reactive(obj) {
  return createReactive(obj)
}
export function shallowReactive(obj) {
  return createReactive(obj, true)
}

export function readonly(obj) {
  return createReactive(obj, false, true)
}

export function shallowReadonly(obj) {
  return createReactive(obj, true, true)
}
